package it.unibo.imola.toolbox.javacv;

import static com.googlecode.javacv.cpp.opencv_core.*;
import static com.googlecode.javacv.cpp.opencv_highgui.*;
import static com.googlecode.javacv.cpp.opencv_imgproc.*;
import it.unibo.imola.parser.visitor.entity.Image;
import it.unibo.imola.parser.visitor.entity.ToolBox;
import it.unibo.imola.utils.ExceptionUtils;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Locale;

import org.apache.commons.math.random.RandomData;
import org.apache.commons.math.random.RandomDataImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.javacv.cpp.opencv_core.CvSize;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

public class JavaCVToolBox implements ToolBox {

	private static RandomData generator = new RandomDataImpl();
	private Logger logger = LoggerFactory.getLogger(getClass());

	@Override
	public boolean exists(String name, Class<?>[] parameterTypes) throws Exception {
		if (name == null)
			throw new IllegalArgumentException("name == null");
		if (parameterTypes == null)
			throw new IllegalArgumentException("parameterTypes == null");
		try {

			Method method = null;
			if (parameterTypes.length == 0) {
				method = getClass().getMethod(name.toLowerCase(Locale.ENGLISH));
			} else if (parameterTypes.length > 0) {
				method = getClass().getMethod(name.toLowerCase(Locale.ENGLISH), parameterTypes);
			}

			if (method == null) {
				return false;
			}
		} catch (NoSuchMethodException e) {
			if (logger.isErrorEnabled())
				logger.error(ExceptionUtils.getMessageForDebugLogging(e));
			return false;
		}

		return true;
	}

	@Override
	public void exec(String name, final Object[] actualParameters) throws Exception {
		if (name == null)
			throw new IllegalArgumentException("name == null");
		if (actualParameters == null)
			throw new IllegalArgumentException("actualParameters == null");

		try {
			Class<?>[] parameterTypes = new Class<?>[actualParameters.length];
			for (int i = 0; i < actualParameters.length; i++) {
				parameterTypes[i] = actualParameters[i].getClass();
			}

			Method method = null;
			if (parameterTypes.length == 0) {
				method = getClass().getMethod(name.toLowerCase(Locale.ENGLISH));
			} else if (parameterTypes.length > 0) {
				method = getClass().getMethod(name.toLowerCase(Locale.ENGLISH), parameterTypes);
			}

			if (method == null) {
				throw new Exception("method == null");
			}
			
			/*******************************************/		
//			CVTask task = new CVTask(method, actualParameters);
//			executor.execute(task);
//			executor.
			/*******************************************/
			method.invoke(this, actualParameters);
		} catch (InvocationTargetException e) {
			if (logger.isErrorEnabled())
				logger.error(ExceptionUtils.getMessageForErrorLogging(e));
			throw new Exception(e.getTargetException());
		}
	}

	// /* Operatori di interfaccia (base) */

	// LOAD("lenna.jpg", input);
	@Override
	public void load(String filename, Image dst) throws Exception {
		if (filename == null)
			throw new IllegalArgumentException("filename == null");
		if (dst == null)
			throw new IllegalArgumentException("dst == null");
		File img_file = new File(filename);
		if (!img_file.exists()) {
			if (logger.isErrorEnabled()) {
				logger.error("!img_file.exists()");
			}
			throw new Exception(String.format("file \"%1s\" does not exists", filename));

		}
		IplImage img = cvLoadImage(filename, CV_LOAD_IMAGE_GRAYSCALE);
		dst.setImageRef(img);
	}

	//
	// SAVE(output,"lennaOut.jpg");
	@Override
	public void save(String filename, Image src) throws Exception {
		if (filename == null)
			throw new IllegalArgumentException("filename == null");
		if (src == null)
			throw new IllegalArgumentException("src == null");
		if (src.getImageRef() == null)
			throw new Exception("src.getImageRef() == null");

		IplImage img = (IplImage) (src.getImageRef());
		cvSaveImage(filename, img);
	}

	// SHOW(output,"windowName");
	@Override
	public void show(String windowname, Image src) throws Exception {
		if (windowname == null)
			throw new IllegalArgumentException("windowname == null");
		if (src == null)
			throw new IllegalArgumentException("src == null");
		if (src.getImageRef() == null)
			throw new Exception("src.getImageRef() == null");

		IplImage img = (IplImage) (src.getImageRef());
		cvShowImage(windowname, img);
	}

	// CLOSE("windowName");
	@Override
	public void close(String windowname) throws Exception {
		if (windowname == null)
			throw new IllegalArgumentException("windowName == null");
		cvDestroyWindow(windowname);
	}
	
	@Override
	public void closeall() throws Exception {
		cvDestroyAllWindows();
	}
	@Override
	public void waitkey(Integer delay) throws Exception {
		if (delay < 0)
			throw new IllegalArgumentException("delay < 0");
		cvWaitKey(0);
	}

	//
	// /* Generatori di Rumore */
	//
	// IID_NOISE(input,output,sigmanoise);
	@Override
	public void iid_noise(Image input, Image output, Float sigma) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (sigma <= 0)
			throw new IllegalArgumentException("sigma <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		int noise_value;
		double noise;

		int cur_value;
		int new_value;

		ByteBuffer in_buf = in_img.getByteBuffer();
		ByteBuffer out_buf = out_img.getByteBuffer();

		for (int i = 0; i < in_img.height(); i++) {
			for (int j = 0; j < in_img.width(); j++) {
				noise = generator.nextGaussian(0, sigma);
				noise_value = (int) noise;

				cur_value = (in_buf.get(i * in_img.widthStep() + j * in_img.nChannels()) + 256) % 256;

				new_value = (int) (noise_value + cur_value);
				if (new_value > 255) {
					new_value = 255;
				} else if (new_value < 0) {
					new_value = 0;
				}

				out_buf.put(i * out_img.widthStep() + j * out_img.nChannels(), (byte) new_value);
			}
		}
	}

	// IMP_NOISE(input,output,sigmanoise);
	@Override
	public void imp_noise(Image input, Image output, Float percentage, Integer noisetype) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (percentage <= 0 || percentage >= 1)
			throw new IllegalArgumentException("percentage <= 0 || percentage >=1");
		if (noisetype < 0 || noisetype > 2)
			throw new IllegalArgumentException("noisetype can be only '0' (salt noise) or '1' (pepper noise) or '2' (salt&pepper noise)");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvCopy(in_img, out_img);
		int index = 0;
		int noisevalue = 0;

		ByteBuffer out_buf = out_img.getByteBuffer();

		for (int i = 0; i < percentage * in_img.width() * in_img.height(); i++) {
			index = generator.nextInt(0, out_img.imageSize() - 1);
			for (int j = 0; j < out_img.nChannels(); j++) {
				switch (noisetype) {
				case 0:
					out_buf.put(index + j, (byte) 0);
					break;
				case 1:
					out_buf.put(index + j, (byte) 255);
					break;
				case 2:
					noisevalue = generator.nextInt(0, 1);
					out_buf.put(index + j, (byte) (noisevalue == 0 ? 0 : 255));
					break;
				}
			}
		}
	}

	//
	// /* Operatori Morfologici */
	//
	// ERODE(output,output,2);
	@Override
	public void erode(Image input, Image output, Integer rows, Integer cols, Integer shape, Integer iterations) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (rows <= 0 || (rows % 2) == 0)
			throw new IllegalArgumentException("rows <= 0 || (rows % 2) == 0");
		if (cols <= 0 || (cols % 2) == 0)
			throw new IllegalArgumentException("cols <= 0 || (cols % 2) == 0");
		if (shape < 0 || shape > 2)
			throw new IllegalArgumentException("shape can be only '0' (RECTANGLE) or '1' (CROSS) or '2' (ELLIPSE)");
		if (iterations <= 0)
			throw new IllegalArgumentException("iterations <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		/*
		 * IplConvKernel* cvCreateStructuringElementEx (int cols, int rows, int
		 * anchorX, int anchorY, int shape, int* values=NULL)
		 * 
		 * CV_SHAPE_RECT = 0 CV_SHAPE_CROSS = 1 CV_SHAPE_ELLIPSE = 2
		 */
		IplConvKernel kernel = null;

		switch (shape) {
		case 0:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_RECT, null);
			break;
		case 1:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_CROSS, null);
			break;
		case 2:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_ELLIPSE, null);
			break;
		}

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvErode(in_img, out_img, kernel, iterations);

	}

	// DILATE(output,output,2);
	@Override
	public void dilate(Image input, Image output, Integer rows, Integer cols, Integer shape, Integer iterations) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (rows <= 0 || (rows % 2) == 0)
			throw new IllegalArgumentException("rows <= 0 || (rows % 2) == 0");
		if (cols <= 0 || (cols % 2) == 0)
			throw new IllegalArgumentException("cols <= 0 || (cols % 2) == 0");
		if (shape < 0 || shape > 2)
			throw new IllegalArgumentException("shape can be only '0' (RECTANGLE) or '1' (CROSS) or '2' (ELLIPSE)");
		if (iterations <= 0)
			throw new IllegalArgumentException("iterations <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		/*
		 * IplConvKernel* cvCreateStructuringElementEx (int cols, int rows, int
		 * anchorX, int anchorY, int shape, int* values=NULL)
		 * 
		 * CV_SHAPE_RECT = 0 CV_SHAPE_CROSS = 1 CV_SHAPE_ELLIPSE = 2
		 */
		IplConvKernel kernel = null;

		switch (shape) {
		case 0:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_RECT, null);
			break;
		case 1:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_CROSS, null);
			break;
		case 2:
			kernel = cvCreateStructuringElementEx(cols, rows, (cols - 1) / 2, (rows - 1) / 2, CV_SHAPE_ELLIPSE, null);
			break;
		}

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvDilate(in_img, out_img, kernel, iterations);
	}

	// /* Operatori Puntuali */
	//
	// GLOBAL_THRES(input,output,120);
	@Override
	public void global_thres(Image input, Image output, Integer thres) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (thres <= 0 || thres >= 255)
			throw new IllegalArgumentException("thres <= 0 || thres >= 255");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		int cur_value;

		ByteBuffer in_buf = in_img.getByteBuffer();
		ByteBuffer out_buf = out_img.getByteBuffer();

		for (int i = 0; i < in_img.height(); i++) {
			for (int j = 0; j < in_img.width(); j++) {

				cur_value = (in_buf.get(i * in_img.widthStep() + j * in_img.nChannels()) + 256) % 256;

				if (cur_value <= thres) {
					out_buf.put(i * in_img.widthStep() + j * in_img.nChannels(), (byte) 0);
				} else
					out_buf.put(i * in_img.widthStep() + j * in_img.nChannels(), (byte) 255);
			}
		}
	}

	// CONSTRAST_STRETCH(input,output,min,max);
	@Override
	public void contrast_stretch(Image input, Image output, Integer min, Integer max) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (min < 0 || min > 255)
			throw new IllegalArgumentException("min < 0 || min > 255");
		if (max < 0 || max > 255 || max < min)
			throw new IllegalArgumentException("max < 0 || max > 255 || max < min");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		ByteBuffer out_buf = out_img.getByteBuffer();
		ByteBuffer in_buf = in_img.getByteBuffer();

		int cur_value; // pIn
		int new_value; // pOut

		for (int i = 0; i < in_img.height(); i++) {
			for (int j = 0; j < in_img.width(); j++) {
				cur_value = (in_buf.get(i * in_img.widthStep() + j * in_img.nChannels()) + 256) % 256;

				if (cur_value < min)
					new_value = 0;
				else if (cur_value > max)
					new_value = 255;
				else {
					new_value = (255 / (max - min)) * (cur_value - min);
					if (new_value > 255) {
						new_value = 255;
					} else if (new_value < 0) {
						new_value = 0;
					}
				}

				out_buf.put(i * out_img.widthStep() + j * out_img.nChannels(), (byte) new_value);
			}
		}
	}

	// // POWER(input,output,gamma);
	// public void power(Image input, Image output, Float gamma) throws
	// Exception {
	// if (input == null)
	// throw new IllegalArgumentException("input == null");
	// if (output == null)
	// throw new IllegalArgumentException("output == null");
	// if (input.getImageRef() == null)
	// throw new IllegalArgumentException("input.getImageRef() == null");
	//
	// IplImage in_img = (IplImage) input.getImageRef();
	// IplImage out_img = (IplImage) output.getImageRef();
	//
	// if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
	// cvReleaseImage((IplImage) output.getImageRef());
	// }
	//
	// CvSize size = new CvSize(in_img.width(), in_img.height());
	// output.setImageRef(cvCreateImage(size, in_img.depth(),
	// in_img.nChannels()));
	// out_img = (IplImage) output.getImageRef();
	//
	// cvPow(in_img, out_img, gamma);
	// }

	// EQUALIZE(input,output);
	@Override
	public void equalize(Image input, Image output) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvEqualizeHist(in_img, out_img);
	}

	//
	// /* Operatori Locali */
	//
	// MEAN_SMOOTHING(input,output,3);
	@Override
	public void mean_smoothing(Image input, Image output, Integer kernelsize) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (kernelsize <= 0)
			throw new IllegalArgumentException("kernelsize <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvSmooth(in_img, out_img, CV_BLUR, kernelsize);
	}

	// GAUSS_SMOOTHING(input,output,sigma);
	@Override
	public void gauss_smoothing(Image input, Image output, Float sigma) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (sigma <= 0)
			throw new IllegalArgumentException("sigma <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvSmooth(in_img, out_img, CV_GAUSSIAN, 0, 0, sigma, 0);
	}

	// BILATERAL_SMOOTHING(input,output,colorssigma,spatialsigma);
	@Override
	public void bilateral_smoothing(Image input, Image output, Float colorsigma, Float spatialsigma) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (colorsigma <= 0)
			throw new IllegalArgumentException("colorsigma <= 0");
		if (spatialsigma <= 0)
			throw new IllegalArgumentException("spatialsigma <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvSmooth(in_img, out_img, CV_BILATERAL, 0, 0, colorsigma, spatialsigma);
	}

	// MEDIAN_SMOOTHING(input,output,3);
	@Override
	public void median_smoothing(Image input, Image output, Integer kernelsize) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (kernelsize <= 0)
			throw new IllegalArgumentException("kernelsize <= 0");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvSmooth(in_img, out_img, CV_MEDIAN, kernelsize);
	}
	@Override
	public void sum(Image src1, Image src2, Image output) throws Exception {
		if (src1 == null)
			throw new IllegalArgumentException("src1 == null");
		if (src2 == null)
			throw new IllegalArgumentException("src2 == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (src1.getImageRef() == null)
			throw new IllegalArgumentException("src1.getImageRef() == null");
		if (src2.getImageRef() == null)
			throw new IllegalArgumentException("src2.getImageRef() == null");

		IplImage src1_img = (IplImage) src1.getImageRef();
		IplImage src2_img = (IplImage) src2.getImageRef();

		if (areNotCompatible(src1_img, src2_img)) {
			throw new IllegalArgumentException("");
		}

		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(src1_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(src1_img.width(), src1_img.height());
		output.setImageRef(cvCreateImage(size, src1_img.depth(), src1_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvAdd(src1_img, src2_img, out_img, null);

	}
	@Override
	public void diff(Image src1, Image src2, Image output) throws Exception {
		if (src1 == null)
			throw new IllegalArgumentException("src1 == null");
		if (src2 == null)
			throw new IllegalArgumentException("src2 == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (src1.getImageRef() == null)
			throw new IllegalArgumentException("src1.getImageRef() == null");
		if (src2.getImageRef() == null)
			throw new IllegalArgumentException("src2.getImageRef() == null");

		IplImage src1_img = (IplImage) src1.getImageRef();
		IplImage src2_img = (IplImage) src2.getImageRef();

		if (areNotCompatible(src1_img, src2_img)) {
			throw new IllegalArgumentException("");
		}

		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(src1_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(src1_img.width(), src1_img.height());
		output.setImageRef(cvCreateImage(size, src1_img.depth(), src1_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvSub(src1_img, src2_img, out_img, null);
	}
	@Override
	public void absdiff(Image src1, Image src2, Image output) throws Exception {
		if (src1 == null)
			throw new IllegalArgumentException("src1 == null");
		if (src2 == null)
			throw new IllegalArgumentException("src2 == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (src1.getImageRef() == null)
			throw new IllegalArgumentException("src1.getImageRef() == null");
		if (src2.getImageRef() == null)
			throw new IllegalArgumentException("src2.getImageRef() == null");

		IplImage src1_img = (IplImage) src1.getImageRef();
		IplImage src2_img = (IplImage) src2.getImageRef();

		if (areNotCompatible(src1_img, src2_img)) {
			throw new IllegalArgumentException("");
		}

		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(src1_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(src1_img.width(), src1_img.height());
		output.setImageRef(cvCreateImage(size, src1_img.depth(), src1_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvAbsDiff(src1_img, src2_img, out_img);
	}
	@Override
	public void and(Image src1, Image src2, Image output) throws Exception {
		if (src1 == null)
			throw new IllegalArgumentException("src1 == null");
		if (src2 == null)
			throw new IllegalArgumentException("src2 == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (src1.getImageRef() == null)
			throw new IllegalArgumentException("src1.getImageRef() == null");
		if (src2.getImageRef() == null)
			throw new IllegalArgumentException("src2.getImageRef() == null");

		IplImage src1_img = (IplImage) src1.getImageRef();
		IplImage src2_img = (IplImage) src2.getImageRef();

		if (areNotCompatible(src1_img, src2_img)) {
			throw new IllegalArgumentException("");
		}

		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(src1_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(src1_img.width(), src1_img.height());
		output.setImageRef(cvCreateImage(size, src1_img.depth(), src1_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvAnd(src1_img, src2_img, out_img, null);
	}
	@Override
	public void or(Image src1, Image src2, Image output) throws Exception {
		if (src1 == null)
			throw new IllegalArgumentException("src1 == null");
		if (src2 == null)
			throw new IllegalArgumentException("src2 == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (src1.getImageRef() == null)
			throw new IllegalArgumentException("src1.getImageRef() == null");
		if (src2.getImageRef() == null)
			throw new IllegalArgumentException("src2.getImageRef() == null");

		IplImage src1_img = (IplImage) src1.getImageRef();
		IplImage src2_img = (IplImage) src2.getImageRef();

		if (areNotCompatible(src1_img, src2_img)) {
			throw new IllegalArgumentException("");
		}

		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(src1_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(src1_img.width(), src1_img.height());
		output.setImageRef(cvCreateImage(size, src1_img.depth(), src1_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvOr(src1_img, src2_img, out_img, null);
	}
	@Override
	public void not(Image input, Image output) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvNot(in_img, out_img);
	}
	@Override
	public void set(Image input, Integer value) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (value == null)
			throw new IllegalArgumentException("value == null");
		if (value < 0 || value > 255)
			throw new IllegalArgumentException("value < 0 || value > 255");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();

		ByteBuffer in_buf = in_img.getByteBuffer();

		for (int i = 0; i < in_img.height(); i++) {
			for (int j = 0; j < in_img.width(); j++) {
				in_buf.put(i * in_img.widthStep() + j * in_img.nChannels(), value.byteValue());
			}
		}
	}
	@Override
	public void invert(Image input, Image output) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		ByteBuffer in_buf = in_img.getByteBuffer();
		ByteBuffer out_buf = out_img.getByteBuffer();
		int cur_value = 0;

		for (int i = 0; i < in_img.height(); i++) {
			for (int j = 0; j < in_img.width(); j++) {
				cur_value = in_buf.get(i * in_img.widthStep() + j * in_img.nChannels());
				out_buf.put(i * in_img.widthStep() + j * in_img.nChannels(), (byte) (255 - cur_value));
			}
		}
	}
	@Override
	public void copy(Image input, Image output) throws Exception {
		if (input == null)
			throw new IllegalArgumentException("input == null");
		if (output == null)
			throw new IllegalArgumentException("output == null");
		if (input.getImageRef() == null)
			throw new IllegalArgumentException("input.getImageRef() == null");

		IplImage in_img = (IplImage) input.getImageRef();
		IplImage out_img = (IplImage) output.getImageRef();

		if (output.getImageRef() != null && areNotCompatible(in_img, out_img)) {
			cvReleaseImage((IplImage) output.getImageRef());
		}

		CvSize size = new CvSize(in_img.width(), in_img.height());
		output.setImageRef(cvCreateImage(size, in_img.depth(), in_img.nChannels()));
		out_img = (IplImage) output.getImageRef();

		cvCopy(in_img, out_img);
	}
	
	private boolean areNotCompatible(IplImage src1_img, IplImage src2_img) {

		if (src1_img.width() != src2_img.width()) {
			if (logger.isDebugEnabled())
				logger.debug("src1_img.width() != src2_img.width()");
			return true;
		}

		if (src1_img.height() != src2_img.height()) {
			if (logger.isDebugEnabled())
				logger.debug("src1_img.height() != src2_img.height()");
			return true;
		}

		if (src1_img.depth() != src2_img.depth()) {
			if (logger.isDebugEnabled())
				logger.debug("src1_img.depth() != src2_img.depth()");
			return true;
		}

		if (src1_img.nChannels() != src2_img.nChannels()) {
			if (logger.isDebugEnabled())
				logger.debug("src1_img.nChannels() != src2_img.nChannels()");
			return true;
		}
		return false;
	}

}
//private ExecutorService executor = Executors.newSingleThreadExecutor();
//
//private class CVTask implements Runnable{
//	private final Object[] actualParameters;
//	private final Method method;
//	private Exception ex = null;		
//	/**
//	 * @return the ex
//	 */
//	public Exception getEx() {
//		return ex;
//	}
//	public CVTask(Method method, Object[] actualParameters){
//		this.actualParameters = actualParameters;
//		this.method = method;
//	}
//	@Override
//	public void run() {
//		try {
//			method.invoke(this, actualParameters);
//		} catch (Exception e) {
//			ex=e;
//		}
//	}
//	
//}